package com.daxue.configtest.utils;

import com.daxue.configtest.exception.ExceptionWrapper;
import com.daxue.configtest.exception.impl.BadArgumentException;
import com.daxue.configtest.exception.json.JsonGenericException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.NonNull;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.daxue.configtest.others.CollectionsUtils.listOf;
import static com.daxue.configtest.others.CollectionsUtils.mapOf;
import static com.fasterxml.jackson.databind.node.JsonNodeType.ARRAY;
import static com.fasterxml.jackson.databind.node.JsonNodeType.MISSING;

/**
 * @author daxue0929
 * @date 2023/3/26
 */
@Slf4j
@UtilityClass
class ObjectMapperUtils {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    public static ObjectMapper getMapper() {
        return MAPPER;
    }

    public static String asString(Object value) throws JsonProcessingException {
        try {
            return MAPPER.writeValueAsString(value);
        } catch (JsonProcessingException e) {
            throw new JsonGenericException(e);
        }
    }

    public static String asStringSerenely(Object value) {
        try {
            return MAPPER.writeValueAsString(value);
        } catch (JsonProcessingException e) {
            log.error("[ObjectMapper] failed to convert object to JSON string.", e);
            return "";
        }
    }

    public static <T> T asValue(
        String value, Class<T> valueType
    ) throws JsonProcessingException {
        try {
            return MAPPER.readValue(value, valueType);
        } catch (IOException e) {
            throw new JsonGenericException(e);
        }
    }

    public static <T> T asValue(
        String value, TypeReference<T> valueTypeRef
    ) throws JsonProcessingException {
        try {
            return MAPPER.readValue(value, valueTypeRef);
        } catch (IOException e) {
            throw new JsonGenericException(e);
        }
    }

    public static <T> T toValue(
        Object fromValue, Class<T> toValueType
    ) throws JsonProcessingException {
        try {
            return MAPPER.convertValue(fromValue, toValueType);
        } catch (IllegalArgumentException e) {
            throw new JsonGenericException(e);
        }
    }

    public static <T> T toValue(
        Object fromValue, TypeReference<T> toValueTypeRef
    ) throws JsonProcessingException {
        try {
            return MAPPER.convertValue(fromValue, toValueTypeRef);
        } catch (IllegalArgumentException e) {
            throw new JsonGenericException(e);
        }
    }

    public static <T> T fromTree(
        TreeNode tree, Class<T> toValueType
    ) throws JsonProcessingException {
        try {
            return MAPPER.treeToValue(tree, toValueType);
        } catch (IllegalArgumentException e) {
            throw new JsonGenericException(e);
        }
    }

    public static <T> T fromTree(
        TreeNode tree, TypeReference<T> toValueTypeRef
    ) throws JsonProcessingException {
        try {
            return MAPPER.readValue(
                MAPPER.treeAsTokens(tree), MAPPER.getTypeFactory().constructType(toValueTypeRef));
        } catch (IllegalArgumentException | IOException e) {
            throw new JsonGenericException(e);
        }
    }

    public static JsonNode toTree(
        Object fromValue
    ) throws JsonProcessingException {
        try {
            return MAPPER.valueToTree(fromValue);
        } catch (IllegalArgumentException e) {
            throw new JsonGenericException(e);
        }
    }

    // FIXME: throw checked exception
    public static <T> T fromMap(Map<String, Object> map, Class<T> clazz) {
        return ExceptionWrapper.SupplierWrapFunction.wrap(
            () -> toValue(map, clazz),
            IllegalArgumentException::new
        );
    }

    // FIXME: throw checked exception
    public static <T> Map<String, Object> toMap(T obj) {
        return ExceptionWrapper.SupplierWrapFunction.wrap(
            () -> toValue(obj, new TypeReference<Map<String, Object>>() {}),
            IllegalArgumentException::new
        );
    }

    /*
    same as lodash '_.get' see  https://lodash.com/docs/4.17.15#get
     */
    public static String getPath(@NonNull Object map, @NonNull String path) throws JsonProcessingException {
        JsonNode jsonNode = toValue(map, JsonNode.class);
        if (StringUtils.isEmpty(path)) {
            return jsonNode.toString();
        }
        final String[] filedNames = path.split("[.\\[\\]]");
        for (final String fieldName : filedNames) {
            if (isInteger(fieldName) && jsonNode.getNodeType().equals(ARRAY)) {
                jsonNode = jsonNode.path(Integer.valueOf(fieldName));
            } else {
                jsonNode = jsonNode.path(fieldName);
            }
            if (jsonNode.getNodeType().equals(MISSING)) {
                return null;
            }
        }
        //if not use asText string will be "somebody" not somebody
        return jsonNode.isTextual() ? jsonNode.asText() : jsonNode.toString();
    }

    /*
     fastest way check String is Integer https://stackoverflow.com/a/237321
     */
    private static boolean isInteger(String str) {
        if (str == null) {
            return false;
        }
        int length = str.length();
        if (length == 0) {
            return false;
        }
        for (int i = 0; i < length; i++) {
            char c = str.charAt(i);
            if (c <= '/' || c >= ':') {
                return false;
            }
        }
        return true;
    }

    public static Object wrapByKey(String key, Object value) throws BadArgumentException {
        if (!key.contains(".")) {
            return mapOf(key, value);
        }
        final String[] keys = key.split("\\.");
        List<String> keyList = new ArrayList<>();
        //empty remove
        for (final String k : keys) {
            if (k.length() == 0) {
                continue;
            }
            keyList.add(k);
        }
        if (keyList.size() == 0) {
            throw new BadArgumentException(key + " is empty");
        }
        Object realValue = value;
        for (int i = keyList.size() - 1; i >= 0; i--) {
            if ((keyList.get(i).equals("_"))) {
                realValue = listOf(realValue);
            } else {
                realValue = mapOf(keyList.get(i), realValue);
            }
        }
        return realValue;
    }

}
